#/usr/bin/env python
"""
Name: Tank Wars
Author: Hamid Tora, Amin Tora
Date: 05/06/2013
Description: Tank Wars game for ISA 681 course 2013
File: server.py
"""

# ---------- Import Required Modules ----------

import socket, ssl, sys, os, math, time, network, re, threading
from thread import *

# ---------- Weapon ----------------

class Weapon():

    def __init__(self, size, xpos, ypos, speed):
        # weapon used to shoot
            self.size = size
            self.xpos = xpos
            self.ypos = ypos
            self.speed = speed

# ---------- Player Tank Object ----------

class PlayerTank():

    def __init__(self, name):
            # player tank variables
            self.name = name    # pl or p2 for player 1/2
            self.xpos = 0       # tank x position (0 ... 1023)
            self.ypos = 0       # tank y position (0 ... 767)
            self.life = 0       # life level
            self.fuel = 0       # fuel level
            self.turn = False;  # is it my turn? (if turn then shoot, else evade)
            self.weapons = { 's':50, 'm':10, 'l':2 }    
                    # default 50 small, 10 med, 2 large weapons with(range,intensity):
                    # (s:5pixels,10pwrHit; m:10pixels,15pwrHit; l:15pixels,25pwrHit)
            self.connected = False
            self.addr = ''
            self.port = ''
            self.state = 'closed' # states: closed, 
            self.notify = False
            self.update = False
            self.notturn_dist = 10
            self.notturn_dir = 'r'
            self.turn_power = 100
            self.turn_angle = 45
            self.turn_weapon = 's'
            self.setup = False
            self.win = False

    def toggle_connect(self, addr, port):
            self.connected = not(self.connected)
            self.addr = addr
            self.port = port

    def set_state(self, state):
            self.state = state

    def toggle_turn(self):
            self.turn = not(self.turn)

# ---------- Game server class ----------
class gameServer:

    PORT = 31337            # tcp port to bind/listen to
    HOST = 'localhost'      # server ip to bind to
    CLIENTS = 0             # number client connections
    WAITING = True          # server still running?
    LOCK = threading.Lock() # lock for multithreaded data access
    DRIVE = 'p1'                # which client thread is active
    HIGHSCORES = 'scores.dat'   # file keeping high scores
    re_shoot = re.compile('^ack\|shoot\{power:[0-9]{1,3},angle:[0-9]{1,3},weapon:[sml]\}$')
    re_evade = re.compile('^ack\|evade\{dist:[0-9]{1,3},dir:[lr]\}')

    def __init__(self):

        print('GameServer: getting high scores from ' + self.HIGHSCORES + '\n')
        try:
            self.f = open('./' + self.HIGHSCORES, 'r+')
            self.scores = self.f.readlines()
            for self.s in self.scores:
                print(self.s)
        except IOError:
            print('Error opening HIGHSCORES file\n')
            
        print('GameServer: setting socket for tcp:' + str(self.PORT))
        srvsoc= socket.socket()
        try:
            srvsoc.bind(('localhost', 31337))
        except socket.error, msg:
            print('GameServer: bind failed: ' + str(msg[0]) + '- ' + msg[1])
            sys.exit()
        finally:
            print('GameServer: listening for connections on tcp:' + str(self.PORT))
            srvsoc.listen(0)

        self.CLIENTS = 0    # number of client connections (max 2 allowed)
        self.RUNNING = True # are we running server? or shutdown server
        self.P1 = PlayerTank("p1")
        self.P2 = PlayerTank("p2")

        while self.RUNNING:

            sslsoc, addr = srvsoc.accept()
            try:
                datastream = ssl.wrap_socket(sslsoc, server_side = True, certfile = "cert.pem", keyfile = "cert.pem", ssl_version = ssl.PROTOCOL_TLSv1)
            except socket.error, msg:
                print('GameServer: ssl socket error: ' + str(msg[0]) + '- ' + msg[1])
                sslsoc.close()
                sys.exit()
            finally:

                self.LOCK.acquire()
                self.CLIENTS += 1
                self.LOCK.release()

                self.LOCK.acquire()
                if(self.CLIENTS > 2):
                    print('GameServer(' + str(self.CLIENTS) + '): Max clients reached ... not handling this client connection')
                    sslsoc.close()
                    datastream.close()
                    self.CLIENTS -= 1
                    self.LOCK.release()
                else:
                    if(not self.P1.connected):
                        self.P1.toggle_connect(addr[0], str(addr[1]))
                        start_new_thread(self.chandler, ('p1', datastream))
                        print('GameServer(' + str(self.CLIENTS) + '): P1 connection from ' + addr[0] + ':' + str(addr[1]))
                        self.LOCK.release()
                    elif(not self.P2.connected):
                        self.P2.toggle_connect(addr[0], str(addr[1]))
                        start_new_thread(self.chandler, ('p2', datastream))
                        print('GameServer(' + str(self.CLIENTS) + '): P2 connection from ' + addr[0] + ':' + str(addr[1]))
                        self.LOCK.release()
            
        srvsoc.close()

    def callback_calculate(self):

        self.LOCK.acquire()
        if(self.P1.turn):
            self.P2.life -= 40
            if(self.P2.life <= 0):
                self.P1.win = True
            self.P1.turn = False
            self.P1.weapons['s'] -= 1
            self.P2.turn = True
        else:
            self.P1.life -= 40
            if(self.P1.life <= 0):
                self.P1.win = True
            self.P2.turn = False
            self.P2.weapons['s'] -= 1
            self.P1.turn = True

        if(self.DRIVE == 'p1'):
            self.DRIVE = 'p2'
        else:
            self.DRIVE = 'p1'

        self.P1.state = 'rdy2play'
        self.P1.update = True
        self.P2.state = 'rdy2play'
        self.P2.update = True
        self.LOCK.release()

    def projectile_xy(v, a, hs=0.0, g=9.8):
        '''
        This function is directly copied from:
        vegaseat, posted Mar 20th, 2013, 4:22pm
        at site: http://www.daniweb.com/software-development/python/code/450564/projectile-motion-python

        calculate a list of (x, y) projectile motion data points
        where:
            x axis is distance (or range) in meters
            y axis is height in meters
            v is muzzle velocity of the projectile (meter/second)
            a is the firing angle with repsect to ground (radians)
            hs is starting height with respect to ground (meters)
            g is the gravitational pull (meters/second_square)
        '''
        data_xy = []
        t = 0.0
        while True:
            # now calculate the height y
            y = hs + (t * v * math.sin(a)) - (g * t * t)/2
            # projectile has hit ground level
            if y < 0:
                break
            # calculate the distance x
            x = v * math.cos(a) * t
            # append the (x, y) tuple to the list
            data_xy.append((x, y))
            # use the time in increments of 0.1 seconds
            t += 0.1
        return data_xy

    def chandler(self, name, datastream):

        data = ''
        datastream.settimeout(600)  # if client is silent for 10mins we assume disconnect

        self.LOCK.acquire()
        if(name == 'p1'):
            me = self.P1
            me.turn = True
            me.xpos = 100
            me.ypos = 700
            me.life = 100
            me.fuel = 250
            them = self.P2
            them.turn = False
        else:
            me = self.P2
            me.turn = False
            me.xpos = 900
            me.ypos = 700
            me.life = 100
            me.fuel = 250
            them = self.P1
            them.turn = True
        self.LOCK.release()

        while True:
            while (not me.setup):
                data = datastream.read()
                data = data.strip()
                if(data == 'req|scores'):
                    for s in self.scores:
                        datastream.sendall(s)
                self.LOCK.acquire()
                if(me.state == 'closed' and data == 'req|play'):
                    me.set_state('init')
                    datastream.sendall('ack|' + me.name + '\n')
                elif(me.state == 'init' and (data == 'ack|' + me.name)):
                    me.set_state('initack')
                    datastream.sendall('update|player:' + me.name +
                                    ',life:' + str(me.life) +
                                    ',fuel:' + str(me.fuel) +
                                    ',s:' + str(me.weapons['s']) +
                                    ',m:' + str(me.weapons['m']) +
                                    ',l:' + str(me.weapons['l']) + '\n')
                elif(me.state == 'initack' and (data == 'ack|update')):
                    me.set_state('rdy2play')
                    me.setup = True
                    print('GameServer('+name+'): state is - ' + me.state + '\n')
                self.LOCK.release()
                 
            while(me.setup):
                if(me.win):
                    self.LOCK.acquire()
                    datastream.sendall('win|PLAYER ' + me.name + ' WINS!!!\n')
                    stamp = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
                    self.f.write(stamp + ': Player ' + me.name + ' WINS!!!\n')
                    me.state == 'closed'
                    me.setup = False
                    them.state == 'closed'
                    them.setup == False
                    self.LOCK.release()
           
                if(me.update):
                    self.LOCK.acquire()
                    datastream.sendall('update|player:' + me.name +
                                        ',life:' + str(me.life) +
                                        ',fuel:' + str(me.fuel) +
                                        ',s:' + str(me.weapons['s']) +
                                        ',m:' + str(me.weapons['m']) +
                                        ',l:' + str(me.weapons['l']) + '\n')
                    datastream.sendall('update|player:' + them.name +
                                        ',life:' + str(them.life) +
                                        ',fuel:' + str(them.fuel) +
                                        ',s:' + str(them.weapons['s']) +
                                        ',m:' + str(them.weapons['m']) +
                                        ',l:' + str(them.weapons['l']) + '\n')
                    me.update = False
                    self.LOCK.release()
 
                while(not self.DRIVE == me.name):
                    self.LOCK.acquire()
                    if(them.notify and me.state == 'rdy2play'):
                        if(them.state == 'shooting'):
                            datastream.sendall('updateall|player:' + them.name +
                                             ',shoot{power:' + str(them.turn_power) +
                                             ',angle:' + str(them.turn_angle) +
                                             ',weapon:' + them.turn_weapon + '}\n')
                        elif(them.state == 'evading'):
                            datastream.sendall('updateall|player:' + them.name +
                                             ',evade{dist:' + str(them.notturn_dist) +
                                             ',dir:' + them.notturn_dir + '}\n')
                        them.notify = False
                    self.LOCK.release()
                    time.sleep(1)
 
                try:
                    self.LOCK.acquire()
                    if(me.state == 'rdy2play'):
                        datastream.sendall('req|turn\n')
                    self.LOCK.release()

                    if(not me.update or me.win or them.win):
                        data = datastream.read()
                        data = data.strip()

                        if(data == 'req|scores'):
                            for s in self.scores:
                                datastream.sendall(s)
                        if(me.state == 'rdy2play' and me.turn and self.re_shoot.match(data)):
                            self.LOCK.acquire() 
                            me.set_state('shooting')
                            me.notify = True
                            self.LOCK.release()
                            while(me.notify):
                                time.sleep(1)
                            self.callback_calculate()
                        elif(me.state == 'rdy2play' and me.turn and self.re_evade.match(data)):
                            self.LOCK.acquire() 
                            me.set_state('evading')
                            me.notify = True
                            self.LOCK.release()
                            while(me.notify):
                                time.sleep(1)
                            self.callback_calculate()
                    print('GameServer(' + name + '): DRIVER is: ' + self.DRIVE + '\n')

                except socket.error, msg:
                    print('GameServer(' + name +'): error reading from client ' + me.addr + ':' + me.port)
                    break
                finally:
                    if not data:
                        print('GameServer(' + name + '): error reading from client ' + me.addr + ':' + me.port)
                        break
                    print('GameServer(' + name + '): received {' + data + '} from ' + me.addr + ':' + me.port)
        
        self.LOCK.acquire()
        if(self.CLIENTS > 0):
            self.CLIENTS -= 1
            me.toggle_connect(0,0)
        self.LOCK.release()
        try:
            datastream.shutdown(socket.SHUT_RDWR)
            datastream.close()
        except socket.error, msg:
            print('GameServer(' + name + '): socket closed')
        print('GameServer(' + name + '): exited')

        
# ---------- End Game Server class ----------

# ---------- Define Main Function ----------

def main():
    tankWarsServer = gameServer()

# ---------- End Main Function ----------

# This calls the 'main' function when this script is executed
if __name__ == '__main__': main()

